/**
 * video stream
 * Copyright (c) 2009 cmiscm.com
 * date of final modification : 2009. 11. 20 
*/
package com.cmiscm.videos.stream
{
	import com.cmiscm.utils.controls.DelayCall;
	import com.cmiscm.videos.VideoPlayer;
	import com.cmiscm.videos.events.VideoBufferLoadEvent;
	import com.cmiscm.videos.events.VideoEvent;
	import com.cmiscm.videos.events.VideoProgressEvent;
	import com.cmiscm.videos.events.VideoSoundEvent;
	import com.cmiscm.videos.events.VideoTimeEvent;
	
	import flash.display.Sprite;
	import flash.events.Event;
	import flash.events.EventDispatcher;
	import flash.events.NetStatusEvent;
	import flash.events.TimerEvent;
	import flash.media.SoundTransform;
	import flash.media.Video;
	import flash.net.NetConnection;
	import flash.net.NetStream;

	public class CMstream extends EventDispatcher
	{
		private var _connection:NetConnection;
		protected var _stream:NetStream;
		protected var _duration:Number = 0;
		private var _client:Object = new Object();
		protected var _video:Video;
		
		private var _syncConnection:NetConnection;
		protected var _syncStream:NetStream;
		protected var _syncDuration:Number = 0;
		private var _syncClient:Object = new Object();			
		protected var _syncVideo:Video;
		private var _isSync:Boolean = false;
		
		protected var _curMode:int = VideoPlayer.MODE_STOP;
		
		private var _isAddBuffer:Boolean = false;
		
		private var _engin:Sprite = new Sprite();
		
		private var _curBuffer:Number = 0;
		private var _curProgress:Number = 0;
		
		private var _delayCall:DelayCall;
		private var _isDelay:Boolean = false;
		
		private var _bufferState:String = "NetStream.Buffer.Empty";
		
		private var _isDuration:Boolean = false;
		private var _isStart:Boolean = false;
		
		protected var _progress:CMprogress;
		protected var _info:CMinfo; 
		protected var _buffer:CMbufferload;
		protected var _sound:CMsound;
		
		public function CMstream()
		{
			super();
			setting();
		}
		private function setting():void
		{
			_engin.mouseChildren = false;
			_engin.mouseEnabled = false;
		}
		
		
		/********************************************************************************************************
		 * get
		 ******************************************************************************************************** 
		 */		
		
		/**
		 * get curmode 
		 */		
		public function get curmode():int
		{
			return _curMode;
		}
		
		/**
		 * get stream 
		 */		
		public function get stream():NetStream
		{
			return _stream;
		}
		
		/**
		 * get duration 
		 */		
		public function get duration():Number
		{
			return _duration;
		}
		
		
		/********************************************************************************************************
		 * connect
		 ******************************************************************************************************** 
		 */	
		 
		/**
		 * connect
		 */			
		public function connect($video:Video, $command:String=null):void
		{
			_connection = new NetConnection();
			_connection.connect($command);			
			_stream = new NetStream(_connection);			
			_client.onMetaData = onNetStreamMetaData;
			_stream.client = _client;
			_video = $video;
			_video.attachNetStream(_stream);			
			_stream.addEventListener(NetStatusEvent.NET_STATUS, onNetStreamStatus);			
		}
		
		/**
		 * add sync 
		 * @param $video
		 * 
		 */		
		public function addSync($video:Video):void
		{
			_syncConnection = new NetConnection();
			_syncConnection.connect(null);			
			_syncStream = new NetStream(_syncConnection);
			_syncClient.onMetaData = onNetStreamMetaDataSync;
			_syncStream.client = _syncClient;
			_syncVideo = $video;
			_syncVideo.attachNetStream(_syncStream);
			
			var sndTransform:SoundTransform = new SoundTransform();
			sndTransform.volume = 0;
			_syncStream.soundTransform = sndTransform;			
		}
		
		
		/********************************************************************************************************
		 * setting url
		 ******************************************************************************************************** 
		 */	
		 
		/**
		 * set url 
		 */
		public function setUrl($url:String, $buffer_time:int=5, $cache:Boolean=false):void
		{
			if (_stream == null) {
				throw Error("you must connect first");
			} else {
				_isSync = false;
				
				init();	
								
				_stream.bufferTime = $buffer_time;			
				var mov_url:String = $url;
				if ($cache) mov_url = addCache($url);
				_stream.play(mov_url);				
				_stream.pause();
				
				if (_progress != null) _progress.init();
			}
		}
		
		/**
		 * set sync url 
		 */		
		public function syncUrl($url:String, $sync_url:String, $buffer_time:int=5, $cache:Boolean=false, $sync_cache:Boolean=false):void
		{			
			
			if (_stream == null || _syncStream == null) {
				throw Error("you must addSync first");
			} else {
				_isSync = true;
				
				init();	
				
				_stream.bufferTime = $buffer_time;		
				_syncStream.bufferTime = $buffer_time;		
					
				var mov_url:String = $url;
				var sync_url:String = $sync_url;
				if ($cache) mov_url = addCache($url);
				if ($sync_cache) sync_url = addCache($sync_url);
				
				_stream.play(mov_url);				
				_stream.pause();
				
				_syncStream.play(sync_url);				
				_syncStream.pause();
								
				if (_progress != null) _progress.init();
			}
		}
		
		private function addCache($url:String):String
		{
			var mov_url:String = $url;
			if (mov_url.indexOf("?") == -1) {
				mov_url += "?unique="+(new Date()).getTime();
			} else {
				mov_url += "&unique="+(new Date()).getTime();
			}
			return mov_url;
		}
		
		
		/********************************************************************************************************
		 * init / destroy / clear
		 ******************************************************************************************************** 
		 */	
		 		
		/**
		 * init
		 */		
		private function init():void
		{
			clear();					
			_curMode = VideoPlayer.MODE_INIT;	
			
			if (_progress != null) _progress.init();
			if (_info != null) _info.init();
								
			this.dispatchEvent(new VideoEvent(VideoEvent.CHANGE_MODE, _curMode));
		}
		
		/**
		 * destroy 
		 */		
		public function destroy():void
		{		
			clear();
			destroyEngin();
			if (_stream != null) {
				_stream.removeEventListener(NetStatusEvent.NET_STATUS, onNetStreamStatus);	
				_stream = null;
				_connection.close();
				_connection = null;
				_video = null;
				delete _client.onMetaData;
				_client = null;
			}
			if (_syncStream != null) {	
				_syncStream = null;
				_syncConnection.close();
				_syncConnection = null;
				_syncVideo = null;
				delete _syncClient.onMetaData;
				_syncClient = null;
			}
			if (_progress != null) {
				_progress.destroy();
				_progress = null;
			}
			if (_info != null) {
				_info.destroy();
				_info.removeEventListener(VideoTimeEvent.CHANGE_TIME, onTimeChange);
				_info = null;
			}
			if (_buffer != null) {
				_buffer.destroy();
				_buffer.removeEventListener(VideoBufferLoadEvent.BUFFER_PRECENT, onBufferProgress);
				_buffer.removeEventListener(VideoBufferLoadEvent.FINISH_BUFFER_LOAD, onBufferFinish);
				_buffer.removeEventListener(VideoBufferLoadEvent.COMPLETE_DURATION, onDuration);
				_buffer = null;
			}
		}
		
		/**
		 * clear 
		 */		
		public function clear():void
		{
			stopEngin();
			
			if (_stream != null) _stream.close();
			if (_video != null) _video.clear();			
			_duration = 0;
			
			if (_syncStream != null) _syncStream.close();
			if (_syncVideo != null) _syncVideo.clear();			
			_syncDuration = 0;
						
			_curBuffer = 0;
			_curProgress = 0;
			_isDuration = false;
			_isStart = false;
			
			_isAddBuffer = false;
			
			if (_progress != null) _progress.clear();
			if (_info != null) _info.clear();
		}
		
		
		/********************************************************************************************************
		 * control
		 ******************************************************************************************************** 
		 */	
		 
		public function play():void
		{
			_stream.resume();
			if (_isSync) _syncStream.resume();
			_curMode = VideoPlayer.MODE_PLAY;
			startEngin();
			this.dispatchEvent(new VideoEvent(VideoEvent.CHANGE_MODE, _curMode));
		}
		public function pause():void
		{
			_stream.pause();
			if (_isSync) _syncStream.pause();
			_curMode = VideoPlayer.MODE_PAUSE;
			this.dispatchEvent(new VideoEvent(VideoEvent.CHANGE_MODE, _curMode));
		}
		public function stop():void
		{
			_stream.pause();
			_stream.seek(0);
			if (_isSync) {
				_syncStream.pause();
				_syncStream.seek(0);
			}
			_curMode = VideoPlayer.MODE_STOP;
			this.dispatchEvent(new VideoEvent(VideoEvent.CHANGE_MODE, _curMode));
		}
		
		/**
		 * seek 
		 * @param $no
		 * 
		 */		
		public function seek($no:Number):void
		{
			_stream.seek($no);
			if (_isSync) _syncStream.seek($no);
		}
		
		
		/********************************************************************************************************
		 * netstream event
		 ******************************************************************************************************** 
		 */	
		
		/**
		 * check state
		 */
		private function onNetStreamMetaData(evt:Object):void
		{
			_duration = evt.duration;
			_isDuration = true;
			if (_isDuration && _isStart) this.dispatchEvent(new VideoEvent(VideoEvent.START_VIDEO));
		}
		private function onNetStreamStatus(evt:NetStatusEvent):void
		{
			if (evt.info.code == "NetStream.Buffer.Full" || evt.info.code == "NetStream.Buffer.Empty" || evt.info.code == "NetStream.Seek.Notify" || evt.info.code == "NetStream.Seek.InvalidTime") _bufferState = evt.info.code;

			if (evt.info.code == "NetStream.Play.StreamNotFound") {			
	            this.dispatchEvent(new VideoEvent(VideoEvent.ERROR_VIDEO));
	        } else {
	        	if (evt.info.code == "NetStream.Play.Start") {
	        		_isStart = true;
	        		if (_isDuration && _isStart) this.dispatchEvent(new VideoEvent(VideoEvent.START_VIDEO));
	        	}
				if (evt.info.code == "NetStream.Play.Stop") this.dispatchEvent(new VideoEvent(VideoEvent.FINISH_VIDEO));				
				if (evt.info.code == "NetStream.Buffer.Full" || evt.info.code == "NetStream.Buffer.Flush" || evt.info.code == "NetStream.Play.Stop" || evt.info.code == "NetStream.Buffer.Empty") {
		        	if (_isAddBuffer) {
		        		_isAddBuffer = false;
		        		this.dispatchEvent(new VideoEvent(VideoEvent.REMOVE_BUFFER));
		        	}
		        } else {
					if (_curMode == VideoPlayer.MODE_PLAY && !_isAddBuffer) {
						_isAddBuffer = true;
						this.dispatchEvent(new VideoEvent(VideoEvent.ADD_BUFFER));
					}
				}
	        }
		}
		private function onNetStreamMetaDataSync(evt:Object):void
		{
			_syncDuration = evt.duration;
		}
		
		
		/********************************************************************************************************
		 * move engin
		 ******************************************************************************************************** 
		 */	
		 
		/**
		 * engin
		 */		
		private function startEngin():void
		{
			if (_engin.hasEventListener(Event.ENTER_FRAME)) {
				return;
			} else {
				_engin.addEventListener(Event.ENTER_FRAME, onUpdate, false, 0, true);
			}
		}
		private function stopEngin():void
		{
			if (_engin.hasEventListener(Event.ENTER_FRAME)) _engin.removeEventListener(Event.ENTER_FRAME, onUpdate);
		}
		private function onUpdate(evt:Event):void
		{
			var bufferPercent:Number;
			if (_stream.bytesTotal > 4) {
				bufferPercent = _stream.bytesLoaded / _stream.bytesTotal;
			} else {
				bufferPercent = 0;
			}
			
			var currentPercent:Number;
			if (_stream.time == 0 || _duration == 0) {
				currentPercent = 0;
			} else {
				currentPercent = _stream.time / _duration;
			}
			
			if (_info != null) _info.update();
			
			// event
			if (_curProgress != currentPercent || _curBuffer != bufferPercent) {
				_curProgress = currentPercent;
				_curBuffer = bufferPercent;
				this.dispatchEvent(new VideoProgressEvent(VideoProgressEvent.CHANGE_PRECENT, currentPercent, bufferPercent));
			}
			
			// check sync
			if (_isSync && _curMode == VideoPlayer.MODE_PLAY) checkSync();
			
			// progress
			if (_progress != null) {
				_progress.update(bufferPercent, currentPercent);
				/*
				if (bufferPercent == 1 && (_curMode == VideoPlayer.MODE_PAUSE || _curMode == VideoPlayer.MODE_STOP)) {
					if (_delayCall == null) {
						_delayCall = new DelayCall();
						_delayCall.call(1, endDelayStop);
					}				
				} else {
					if (_delayCall != null) {
						_delayCall.cancel();
						_delayCall = null;
					}
				}
				*/
			} else {
				// stop engin
				//if (bufferPercent == 1 && (_curMode == VideoPlayer.MODE_PAUSE || _curMode == VideoPlayer.MODE_STOP)) stopEngin();
			}
		}
		private function destroyEngin():void
		{
			stopEngin();
			_engin = null;
		}
		
		private function endDelayStop(evt:TimerEvent=null):void
		{
			stopEngin();
			_delayCall = null;
		}
		
		/**
		 * check sync 
		 * 
		 */		
		private function checkSync():void
		{
			var gap:Number = _syncStream.time - _stream.time;			
			if (_bufferState == "NetStream.Buffer.Full") _syncStream.resume();
			else _syncStream.pause();			
			if (gap > 0.4 || gap < -0.4) _syncStream.seek(_stream.time);
		}
		
		
		/********************************************************************************************************
		 * progress
		 ******************************************************************************************************** 
		 */	
		 
		/**
		 * add default progress 
		 */		
		public function addDefaultProgressBar($tW:int, $tH:int, $progress_color:uint, $buffer_color:uint, $bg_color:uint, $be_control:Boolean=false, $control_tY:int=0, $control_tH:int=0, $be_hand:Boolean=false, $buffer_limit:int=0):void
		{
			_progress = new CMprogress(this);
			_progress.addDefaultProgress($tW, $tH, $progress_color, $buffer_color, $bg_color, $be_control, $control_tY, $control_tH, $be_hand, $buffer_limit);
		}
		
		
		/********************************************************************************************************
		 * info
		 ******************************************************************************************************** 
		 */	
		 
		/**
		 * add info 
		 */
		public function addInfo():void
		{
			_info = new CMinfo(this);
			_info.addEventListener(VideoTimeEvent.CHANGE_TIME, onTimeChange);
		}
		private function onTimeChange(evt:VideoTimeEvent):void
		{
			this.dispatchEvent(evt);
		}
		
		/**
		 * get debug info 
		 * @return 
		 * 
		 */		
		public function get getDebugInfo():Sprite
		{
			if (_info == null) addInfo();
			_info.debugInfo();
			return _info;
		}
		
		
		/********************************************************************************************************
		 * buffer load
		 ******************************************************************************************************** 
		 */	
		 
		/**
		 * add buffer load 
		 * @param $video
		 * 
		 */		
		public function addBufferLoad($video:Video):void
		{
			_buffer = new CMbufferload($video);
			_buffer.addEventListener(VideoBufferLoadEvent.BUFFER_PRECENT, onBufferProgress);
			_buffer.addEventListener(VideoBufferLoadEvent.FINISH_BUFFER_LOAD, onBufferFinish);
			_buffer.addEventListener(VideoBufferLoadEvent.COMPLETE_DURATION, onDuration);
		}
		
		/**
		 * buffer load 
		 * 
		 */		
		public function bufferLoad($url:String, $cache:Boolean=false):void
		{
			if (_buffer == null) {
				throw Error("you must addBufferLoad first");
			} else {
				_buffer.bufferLoad($url, $cache);
			}
		}
		
		/**
		 * destroy bufferLoad 
		 * 
		 */		
		public function destroyBufferLoad():void
		{
			if (_buffer == null) {
				throw Error("you must addBufferLoad first");
			} else {
				_buffer.removeEventListener(VideoBufferLoadEvent.BUFFER_PRECENT, onBufferProgress);
				_buffer.removeEventListener(VideoBufferLoadEvent.FINISH_BUFFER_LOAD, onBufferFinish);
				_buffer.removeEventListener(VideoBufferLoadEvent.COMPLETE_DURATION, onDuration);
				_buffer.destroy();
				_buffer = null;
			}
		}
		
		/**
		 * buffer event 
		 * @param evt
		 * 
		 */		
		private function onBufferProgress(evt:VideoBufferLoadEvent):void
		{
			this.dispatchEvent(evt);
		}
		private function onBufferFinish(evt:VideoBufferLoadEvent):void
		{
			this.dispatchEvent(evt);
		}
		private function onDuration(evt:VideoBufferLoadEvent):void
		{
			this.dispatchEvent(evt);
		}
		
		
		/********************************************************************************************************
		 * sound
		 ******************************************************************************************************** 
		 */	
		 
		/**
		 * add sound control 
		 * 
		 */		
		public function addSoundControl($first_volume:Number, $cookie:Boolean):void
		{
			_sound = new CMsound(this, $first_volume, $cookie);
			_sound.addEventListener(VideoSoundEvent.CHANGE_VOLUME, onChangeVolume);
			_sound.init();
		}
		private function onChangeVolume(evt:VideoSoundEvent):void
		{
			this.dispatchEvent(evt);
		}
		
		public function changeVolume($volume:Number):void
		{
			if (_sound == null) {
				throw Error("you must addSoundControl first");
			} else {
				_sound.changeVolume($volume);
			}
		}
		
		public function setVolume($volume:Number):void
		{
			if (_sound == null) {
				throw Error("you must addSoundControl first");
			} else {
				_sound.setVolume($volume);
			}
		}
		
		public function mute():void
		{
			if (_sound == null) {
				throw Error("you must addSoundControl first");
			} else {
				_sound.mute();
			}
		}
		
		public function soundOn():void
		{
			if (_sound == null) {
				throw Error("you must addSoundControl first");
			} else {
				_sound.soundOn();
			}
		}
		
		public function soundOff():void
		{
			if (_sound == null) {
				throw Error("you must addSoundControl first");
			} else {
				_sound.soundOff();
			}
		}
		
	}
}